#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Datetime: 2020/11/30 9:51
# @Author  : CHEN Wang
# @Site    : 
# @File    : portfolio_holding_brinson_attribution.py
# @Software: PyCharm 

"""
股票组合brinson归因， 后续以这个脚本为准，以前的逐渐迁移到这个里面
"""


import pandas as pd
from quant_researcher.quant.datasource_fetch.fund_api.fund_nav_related import get_fund_nav
from quant_researcher.quant.datasource_fetch.index_api import index_components
from quant_researcher.quant.performance_attribution.core_functions.holding_analysis.allocation_analysis.holding_based_allocation import \
    get_holding_industry_weight_and_return


def get_stk_hold_indu_brinson_attribution(fund_code, start_date, end_date, holding_begin,
                                          holding_end,
                                          benchmark, industry_type='SW', **kwargs):
    """

    :param start_date:期初持仓日期
    :param end_date:期末持仓日期
    :param pd.DataFrame holding_begin: 开始时点股票组合的详细权重信息，格式如下
    security_code：证券代码
    security_weight：证券权重
        ---------------+-----------------------+
        security_code | security_weight |
        ---------------+-----------------------+
            000333    |         0.0968        |
            000423    |         0.0192        |
            000568    |         0.0879        |
            000596    |         0.0816        |
            000651    |         0.0971        |
        ---------------+-----------------------+
    :param pd.DataFrame holding_end: 结束时点股票组合的详细权重信息，格式如下
    security_code：证券代码
    security_weight：证券权重
        ---------------+-----------------------+
        security_code | security_weight |
        ---------------+-----------------------+
            000333    |         0.0968        |
            000423    |         0.0192        |
            000568    |         0.0879        |
            000596    |         0.0816        |
            000651    |         0.0971        |
        ---------------+-----------------------+
    :param str benchmark: 业绩基准代码  '000300'
    :param holding_ret: 投资组合持仓中由于股票持仓带来的收益根据仓位进行调整（对于基金可以是大类收益率分解得到），不然做Brinson没有意义
    :param str industry_type: brinson归因行业类别， 默认’SW'
    :return:
        1. brinson 归因序列
        all_allocation_ability    0.187729
        all_manage_ability      0.020883
        all_cross_ability          0.264215
        other_ret                  -0.289276
        index_ret                   0.016449
        fund_ret                    0.200000
        active_ret                  0.183551

        2. 行业的归因
        indu_name         allocation_ability  manage_ability  cross_ability
        交通运输                 0.002697        0.000414      -0.000406
        公用事业                 0.001295       -0.002712       0.002672
        化工                       0.002700        0.008869      -0.008799
        医药生物                 0.182394        0.023876       0.260689
        国防军工                -0.000041       -0.000041       0.000041
        家用电器                 0.002323        0.002323      -0.002323
        有色金属                 0.000907        0.001278      -0.001275
        机械设备               -0.000165        0.022753      -0.022182
        汽车                      0.001298        0.001298      -0.001298
        电子                     -0.016743       -0.015593       0.015582
        计算机                  -0.008568       -0.008568       0.008568
        通信                     -0.000893       -0.000893       0.000893
        银行                      0.020524       -0.012123       0.012055

    """
    conn_base = kwargs.pop('conn_base', None)
    conn_ty = kwargs.pop('conn_ty', None)
    conn_stock = kwargs.pop('conn_stock', None)
    # 获取组合的平均行业持仓及行业收益
    ind_weight_avg, ind_ret = get_holding_industry_weight_and_return(start_date, end_date,
                                                                     holding_begin, holding_end,
                                                                     industry_type=industry_type,
                                                                     conn=conn_base)
    holding_ret = (ind_weight_avg * ind_ret).sum()  # 股票部分带来的收益率
    stock_holding_weight = ind_weight_avg.sum()  # 股票部分持仓权重

    # 获取基金该区间的总收益
    nav_df = get_fund_nav(fund_code=fund_code, start_date=start_date, end_date=end_date, conn=conn_ty)
    fund_ret = nav_df['adjusted_nav'].iloc[-1] / nav_df['adjusted_nav'].iloc[0] - 1

    # 获取基准持仓
    benchmark_holding_begin = index_components.get_index_component_weights(benchmark, end_date=start_date,
                                                                           conn=conn_base).drop(
        columns=['index_code', 'end_date'])
    benchmark_holding_end = index_components.get_index_component_weights(benchmark, end_date=end_date,
                                                                         conn=conn_base).drop(
        columns=['index_code', 'end_date'])
    benchmark_holding_begin.columns = ['security_code', 'security_weight']
    benchmark_holding_end.columns = ['security_code', 'security_weight']
    # 获取基准的平均行业持仓及行业收益
    bench_ind_weight_avg, bench_ind_ret = get_holding_industry_weight_and_return(start_date,
                                                                                 end_date,
                                                                                 benchmark_holding_begin,
                                                                                 benchmark_holding_end,
                                                                                 industry_type=industry_type,
                                                                                 conn_base=conn_base,
                                                                                 conn_stock=conn_stock)
    bench_ret = (bench_ind_weight_avg * bench_ind_ret).sum()  # 基准收益率
    bench_ind_weight_avg = bench_ind_weight_avg * stock_holding_weight  # 把基准的股票仓位调整成与基金一样
    weighted_bench_ret = (bench_ind_weight_avg * bench_ind_ret).sum()  # 如果基准的股票仓位与基金一致的收益率

    # 计算基金股票部分的超额收益
    stock_active_ret = holding_ret - weighted_bench_ret

    # 超额收益分解
    allocation_ability = (ind_weight_avg - bench_ind_weight_avg) * bench_ind_ret
    manage_ability = (ind_ret - bench_ind_ret) * bench_ind_weight_avg
    cross_ability = (ind_ret - bench_ind_ret) * (ind_weight_avg - bench_ind_weight_avg)
    indu_active_return = allocation_ability + manage_ability + cross_ability
    attribution = dict()
    attribution['all_allocation_ability'] = allocation_ability.sum()
    attribution['all_manage_ability'] = manage_ability.sum()
    attribution['all_cross_ability'] = cross_ability.sum()
    # attribution['other_ret'] = active_ret - allocation_ability.sum() - manage_ability.sum() - cross_ability.sum()
    attribution['index_ret'] = bench_ret
    attribution['weighted_index_ret'] = weighted_bench_ret
    attribution['fund_ret'] = fund_ret
    attribution['stock_holding_ret'] = holding_ret
    attribution['stock_active_ret'] = stock_active_ret
    total_attribution = pd.Series(attribution)
    total_attribution['end_date'] = end_date
    indu_attribution_df = pd.concat([allocation_ability.rename('allocation_ability'),
                                     manage_ability.rename('manage_ability'),
                                     cross_ability.rename('cross_ability'),
                                     indu_active_return.rename('industry_active_ret'),
                                     ind_weight_avg.rename('industry_weight')],
                                    axis=1).dropna()
    indu_attribution_df['end_date'] = end_date
    return total_attribution, indu_attribution_df


if __name__ == '__main__':
    pass
